package com.caiyi.financial.nirvana.sms.server;

import com.alibaba.fastjson.JSON;
import com.caiyi.financial.nirvana.sms.entity.SmsUser;
import com.caiyi.financial.nirvana.sms.enums.SmsAuditBatchStatusEnum;
import com.caiyi.financial.nirvana.sms.enums.SmsUserAuditTypeEnum;
import com.caiyi.financial.nirvana.sms.enums.SmsUserChargeTypeEnum;
import com.caiyi.financial.nirvana.sms.gateway.cmpp.LongMsgHeader;
import com.caiyi.financial.nirvana.sms.gateway.cmpp.cmpp20.message.CMPPMessage;
import com.caiyi.financial.nirvana.sms.gateway.cmpp.cmpp20.message.body.*;
import com.caiyi.financial.nirvana.sms.jedis.JedisKey;
import com.caiyi.financial.nirvana.sms.jedis.JedisService;
import com.caiyi.financial.nirvana.sms.queue.AlarmSendQueueEntity;
import com.caiyi.financial.nirvana.sms.queue.SendQueueEntity;
import com.caiyi.financial.nirvana.sms.util.CommUtil;
import com.caiyi.financial.nirvana.sms.util.Constant;
import com.caiyi.financial.nirvana.sms.util.CoreSendUtil;
import com.caiyi.financial.nirvana.sms.util.MD5Util;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.AttributeKey;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

public class CmppServerHandler extends SimpleChannelInboundHandler<CMPPMessage> {
    private static Map<String, LongMessageEntity> dataMap = new ConcurrentHashMap<>();
    public static Map<String, AccountHolder> accountHolders = new ConcurrentHashMap<>();
    private static final Logger logger = LoggerFactory.getLogger(CmppServerHandler.class);
    private String account;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, CMPPMessage msg) {
        int commandId = msg.getCommandID();
        //检查连接的登录状态
        if (commandId != CMPPMessage.CMPP_CONNECT && !checkLogin(ctx)) {
            logger.info("未登录状态发来非登录请求消息，关闭连接！");
            ctx.channel().close();
            return;
        }
        CMPPMessage resp = new CMPPMessage();
        resp.setSequenceId(msg.getSequenceId());
        switch (commandId) {
            case CMPPMessage.CMPP_ACTIVE_TEST:
                resp.setCommandID(CMPPMessage.CMPP_ACTIVE_TEST_RESP);
                CMPPActiveTestResp activeTestResp = new CMPPActiveTestResp();
                resp.setBody(activeTestResp);
                ctx.writeAndFlush(resp);
                break;
            case CMPPMessage.CMPP_ACTIVE_TEST_RESP:
                AttributeKey<String> activeKey = AttributeKey.valueOf("active");
                ctx.channel().attr(activeKey).set("");
                break;
            case CMPPMessage.CMPP_CONNECT:
                int status = handleCmppConnect(ctx, msg);
                resp.setCommandID(CMPPMessage.CMPP_CONNECT_RESP);
                CMPPConnectionResp connectResp = new CMPPConnectionResp();
                connectResp.setStatus(status);
                resp.setBody(connectResp);
                ctx.writeAndFlush(resp);
                break;
            case CMPPMessage.CMPP_SUBMIT:
                resp.setSequenceId(msg.getSequenceId());
                CMPPSubmit body = (CMPPSubmit) msg.getBody();
                CMPPSubmitResp submitResp = handlerSubmit(body, ctx);
                resp.setCommandID(CMPPMessage.CMPP_SUBMIT_RESP);
                resp.setBody(submitResp);
                ctx.writeAndFlush(resp).addListener((future) -> logger.info("CMPP_SUBMIT messageId : {}, phone : {}, isDone : {}, isSuccess : {}", submitResp.getMessageId(), body.getDestTerminalId()[0], future.isDone(), future.isSuccess())).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                break;
            case CMPPMessage.CMPP_TERMINATE:
                resp.setCommandID(CMPPMessage.CMPP_TERMINATE_RESP);
                CMPPTerminateResp terminateResp = new CMPPTerminateResp();
                resp.setBody(terminateResp);
                ctx.writeAndFlush(resp);
                ctx.channel().close();
                break;
            //状态报告或者上行回复
            case CMPPMessage.CMPP_DELIVER_RESP:
                handleDeliverResp(msg);
                break;
            default:
                break;
        }
    }

    private void handleDeliverResp(CMPPMessage msg) {
        CMPPDeliverResp body = (CMPPDeliverResp) msg.getBody();
        Long messageId = body.getMessageId();
        //获取accountHolder
        AccountHolder accountHolder = accountHolders.get(this.account);
        if (null == accountHolder) {
            return;
        }
        logger.info("account:{} receive deliver resp, messageId:{} ,deliverCount : {}", this.account, messageId, accountHolder.getDeliverCount().intValue());
        accountHolder.getDeliverCount().incrementAndGet();
    }

    private CMPPSubmitResp handlerSubmit(CMPPSubmit body, ChannelHandlerContext ctx) {
        long start = System.currentTimeMillis();
        CMPPSubmitResp resp = new CMPPSubmitResp();
        //电话号码,只支持单个电话号码的提交,找不到号码所属运营商则报号码错
        String phone = body.getDestTerminalId()[0];
        String content = body.getMessageContent();
        int operator = CommUtil.getPhoneOperator(phone);
        if (operator == 0) {
            logger.error("account:" + this.account + " submit请求 phone:" + phone + " 目的电话号码未找到运营商！");
            resp.setResult((byte) 1);
            return resp;
        }
        String key = String.format(JedisKey.SMS_USER, account);
        String userJson = JedisService.get(key);
        SmsUser user = JSON.parseObject(userJson, SmsUser.class);
        key = String.format(JedisKey.SMS_BALANCE, account);
        String smsNum = JedisService.get(key);
        user.setSmsNum(Integer.parseInt(smsNum));
        //长短信直接跳过
        if (body.getTpUdhi() != 1) {
            //短信签名验证
            key = String.format(JedisKey.SMS_SIGN, user.getAccount());
            Set<String> signList = JedisService.zrevrange(key, 0, -1);
            if (signList == null || signList.isEmpty()) {
                logger.error("account:" + this.account + "短信签名错误");
                resp.setResult((byte) 2);
                return resp;
            }
            String contentSign = null;
            int index = content.lastIndexOf("【");
            boolean with = content.endsWith("】");
            if (with && index != -1) {
                contentSign = content.substring(index);
            }
            with = content.startsWith("【");
            index = content.indexOf("】");
            if (with && index != -1) {
                contentSign = content.substring(0, index + 1);
            }
            //如果不包含签名
            if (contentSign == null) {
                content = signList.iterator().next() + content;
            } else {
                if (user.getAuditType() == SmsUserAuditTypeEnum.TEMPLATE.index && !signList.contains(contentSign)) {
                    logger.error("account:" + this.account + "短信签名错误");
                    resp.setResult((byte) 2);
                    return resp;
                }
            }
        }
        //敏感词过滤
        Set<String> keywords = JedisService.smembers(JedisKey.SMS_KEYWORD);
        for (String keyword : keywords) {
            if (content.contains(keyword)) {
                logger.error("account:" + this.account + "短信包含敏感词");
                resp.setResult((byte) 3);
                return resp;
            }
        }
        /*
         * 预付费账户检测余额是否充足
         * 后付费用户直接增加短信发送数量
         */
        Integer msgCount = CommUtil.splitLongContent(content).size();
        if (user.getChargeType() == SmsUserChargeTypeEnum.BEFORE.index && user.getSmsNum() < msgCount) {
            logger.error("account:" + this.account + "余额不足");
            resp.setResult((byte) 4);
            return resp;
        }
        //扣款，更新至Redis
        key = String.format(JedisKey.SMS_BALANCE, account);
        long count = JedisService.incrBy(key, -msgCount);
        //扣款，添加操作到数据库更新队列
        JedisService.lpush(JedisKey.SMS_QUEUE_BALANCE_UPDATE, user.getId() + "=" + (-msgCount));
        //余额提醒
        remindMsgCount(user, count);
        //生成batchId，用于回复给客户
        String batchId = CommUtil.generateBatchId();
        Long messageId = Long.valueOf(batchId);
        //长短信内容缓存与拼接功能
        if (body.getTpUdhi() == 1) {
            LongMsgHeader msgHeader = body.getMsgHeader();
            if (null == msgHeader || msgHeader.getTotal() < 2) {
                //回复客户消息结构错
                resp.setResult((byte) 1);
                return resp;
            }
            byte serial = msgHeader.getSerial();
            byte total = msgHeader.getTotal();
            byte number = msgHeader.getNumber();
            String longMsgKey = phone + serial;
            LongMessageEntity entity = dataMap.get(longMsgKey);
            if (null == entity || (!entity.getPhone().equals(phone) || entity.getTotal() != total)) {
                //缓存新的长短信
                holdNewLongMsg(messageId, serial, total, number, phone, content);
                //回复客户提交成功
                resp.setMessageId(messageId);
                resp.setResult((byte) 0);
                return resp;
            } else {
                //拼接长短信
                entity.addMessage(number, messageId, content);
                if (!entity.isFinish()) {
                    //长短信拼接没完成，回复客户提交成功
                    resp.setMessageId(messageId);
                    resp.setResult((byte) 0);
                    return resp;
                } else {
                    //长短信拼接完成，进行后续处理
                    content = entity.getFullContent();
                    dataMap.remove(entity.getKey());
                }
            }
        }
        //模板审核
        boolean codeFree = false;
        if (user.getAuditType() == SmsUserAuditTypeEnum.CODE_FREE.index) {
            codeFree = needFree(content);
        }
        if (user.getAuditType() == SmsUserAuditTypeEnum.TEMPLATE.index || codeFree) {
            key = String.format(JedisKey.SMS_TEMPLATE, user.getAccount());
            Set<String> userTemplates = JedisService.smembers(key);
            boolean isMatch = false;
            String matchContent = content.replaceAll("【.*】", "");
            for (String template : userTemplates) {
                //添加开头结尾
                isMatch = Pattern.matches("^" + template + "$", matchContent);
                if (isMatch) break;
            }
            if (!isMatch) {
                codeFree = true;
            }
        }
        //短信内容提交后续处理
        Set<String> phones = new HashSet<>();
        phones.add(phone);
        SendQueueEntity entity = new SendQueueEntity();
        InetSocketAddress socketAddr = (InetSocketAddress) ctx.channel().remoteAddress();
        String ip = socketAddr.getHostString();
        entity.setIp(ip);
        entity.setAccessNumber(body.getSrcId());
        entity.setUid(body.getSrcId());
        entity.setAccount(account);
        entity.setContent(content);
        entity.setPhone(phone);
        entity.setPhoneCount(1);
        entity.setProductId(user.getProductId());
        entity.setBatchId(batchId);
        //进入审核队列
        if (codeFree) {
            entity.setStatus(SmsAuditBatchStatusEnum.AUDIT.index);
            //batch_audit表入库队列
            JedisService.lpush(JedisKey.SMS_QUEUE_BATCH_MSG_AUDIT_DB, JSON.toJSONString(entity));
        } else {
            CoreSendUtil.handlerBusiness(phones, entity, user);
        }
        //回复客户提交成功
        resp.setMessageId(messageId);
        resp.setResult((byte) 0);
        logger.info("account : {}, phone : {}, messageId : {}, send cost : {} ms", account, phone, messageId, System.currentTimeMillis() - start);
        return resp;
    }

    private void remindMsgCount(SmsUser user, long count) {
        try {
            String phone = user.getRemindPhone();
            Integer remindCount = user.getRemindValue();
            if (StringUtils.isBlank(phone)) {
                return;
            }
            if (remindCount == null || count >= remindCount) {
                return;
            }
            String key = String.format(JedisKey.SMS_REMIND_COUNT_ACCOUNT, user.getAccount());
            if (JedisService.exists(key)) {
                logger.info("account {} it has been reminding within 24 hours", user.getAccount());
                return;
            }
            AlarmSendQueueEntity entity = new AlarmSendQueueEntity();
            entity.setPhone(user.getRemindPhone());
            String content = String.format(Constant.SMS_REMIND_CONTENT, user.getAccount(), count, remindCount);
            entity.setContent(content);
            JedisService.lpush(JedisKey.SMS_QUEUE_ALARM_MSG_SEND, JSON.toJSONString(entity));
            JedisService.set(key, "1");
            JedisService.expire(key, Constant.DAY_OF_SECOND);
            logger.info("send reminding sms phone : {}, account : {}", phone, user.getAccount());
        } catch (Exception e) {
            logger.error("BalanceFilter remindMsgCount error ! ", e);
        }
    }

    public boolean needFree(String content) {
        String[] codeFree = Constant.CODE_FREE.split(",");
        for (int i = 0; i < codeFree.length; i++) {
            if (content.contains(codeFree[i])) {
                return false;
            }
        }
        return true;
    }

    //缓存新的长短信
    private void holdNewLongMsg(Long messageId, byte serial, byte total, byte number,
                                String phone, String content) {
        LongMessageEntity entity = new LongMessageEntity(serial, total, phone);
        entity.addMessage(number, messageId, content);
        dataMap.put(entity.getKey(), entity);
    }

    private int handleCmppConnect(ChannelHandlerContext ctx, CMPPMessage msg) {
        CMPPConnection body = (CMPPConnection) msg.getBody();
        String cmppAccount = body.getSpId();
        SmsUser user = null;
        try {
            logger.info("cmpp account : {}, password : {}", cmppAccount, new String(body.getAuthenticator(), "utf-8"));
            String key = String.format(JedisKey.SMS_USER, cmppAccount);
            String userJson = JedisService.get(key);
            user = JSON.parseObject(userJson, SmsUser.class);
        } catch (Exception e) {
            logger.error("find user error ", e);
        }
        if (user == null) {
            //用户未找到
            return 3;
        }
        //验证用户IP
        String accountIp = user.getIp();
        if (StringUtils.isNotBlank(accountIp)) {
            InetSocketAddress socketAddr = (InetSocketAddress) ctx.channel().remoteAddress();
            String clientIP = socketAddr.getHostString();
            List<String> ipList = Arrays.asList(accountIp.split(","));
            if (!ipList.contains(clientIP)) {
                logger.info("account:{}, ip:{} is invalid!, accountIp : {}", cmppAccount, clientIP, accountIp);
                return 2;
            }
        }
        //验证用户密码
        String sTimestamp = String.format("%010d", body.getTimestamp());
        String password = user.getPassword();
        byte[] zeros = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        byte[] authenticatorSource = MD5Util.Md5Byte(cmppAccount + new String(zeros) + password + sTimestamp);
        if (!Arrays.equals(authenticatorSource, body.getAuthenticator())) {
            return 3;
        }
        AccountHolder holder = accountHolders.get(cmppAccount);
        if (holder != null) {
            holder.shutdown();
        }
        holder = new AccountHolder(cmppAccount, ctx);
        holder.start();
        accountHolders.put(cmppAccount, holder);
        //验证成功
        AttributeKey<String> accountKey = AttributeKey.valueOf("account");
        ctx.channel().attr(accountKey).set(cmppAccount);
        this.account = cmppAccount;
        return 0;
    }

    private boolean checkLogin(ChannelHandlerContext ctx) {
        AttributeKey<String> accountKey = AttributeKey.valueOf("account");
        String account = ctx.channel().attr(accountKey).get();
        if (StringUtils.isBlank(account)) {
            return false;
        }
        return true;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        logger.info("CmppServerHandler exceptionCaught  {}", cause.getMessage());
    }

}
